/**
 * @file   GuiCtrlText.java
 * @author Enrique Madrid <e.madrid@lpcsite.com>
 * @date   Sat Sep  8 17:26:21 2007
 * 
 * @brief  
 * 
 * 
 */

package sim.framework.gui;

import javax.microedition.lcdui.*;
import sim.framework.*;
import sim.framework.font.*;


public class GuiCtrlText extends GuiCtrl {

    public final int PROP_NUMERIC = 0x1;
    public final int PROP_MULTILINE = 0x2;
    public final int PROP_HIDECURSOR = 0x4;
    public final int PROP_READONLY = 0x08;
    public final int PROP_VCENTER = 0x10;
    public final int PROP_VSCROLL = 0x20;

    private final int BUFFER_INIT_SIZE = 160;

    private final int CURSOR_PERIOD = 500;
    private final int CHAR_TIMEOUT = 2*CURSOR_PERIOD;

    private final char CHAR_CONTROL = '\r';

    /**
     * Para definir el mapeo de los diferentes teclados debemos usar
     * la palabra m_Keypad seguida del ISO que implementa.
     * Por el momento estamos enfocados ISO/IEC 9995-8 (1994).
     */

    // Averiguar si siguiente definicion es conforme ISO/IEC 9995-8 (1994).
    private char m_Keypad[][] = {
        {' ', '0'},
        {'.', ',', '?', '!', '+', '-', '_', '1'},
        {'a', 'b', 'c', '2'},
        {'d', 'e', 'f', '3'},
        {'g', 'h', 'i', '4'},
        {'j', 'k', 'l', '5'},
        {'m', 'n', 'o', '6'},
        {'p', 'q', 'r', 's', '7'},
        {'t', 'u', 'v', '8' },
        {'w', 'x', 'y', 'z', '9'}
    };

    // Caracteres de control. (herencia UNIX)
    private char[] m_Linefeed = {CHAR_CONTROL, '\n'};

    // Propiedades de edición
    protected StringBuffer m_Buffer = new StringBuffer(BUFFER_INIT_SIZE);
    protected int m_CursorPos;

    // Propiedades para control de edición
    private int m_LastKey = Gui.KEY_NONE;
    private int m_CharPos = 0;
    private int m_Counter = 0;
    private boolean m_Pressed = false;

    // Propiedades para control de visualización
    private boolean m_Cursor = true;
    private int m_Lines = 0;
    private int m_CursorX = 0;
    private int m_CursorY = 0;

    public GuiCtrlText(int id, final FontSingle font) {
	super(id, font);
    }

    public final int getType() {
	return GuiCtrl.TEXT;
    }

    protected boolean focusable() {
	return true;
    }

    protected void setFocus(boolean state) {
	m_Cursor = state;
	super.setFocus(state);
    }

    protected int minHeight() {
	if ((getProperties() & PROP_MULTILINE) > 0) {
	    return GuiCtrl.MAX_SIZE_AVAILABLE;
	}
	return super.minHeight();
    }

    protected int getPeriod() {
	return CURSOR_PERIOD;
    }

    public int getViewWidth() {
	return dx - 2*GuiCtrl.VIEW_HMARGIN;
    }

    public int getViewHeight() {
	return dy - 2*GuiCtrl.VIEW_VMARGIN;
    }

    protected void paint(Graphics g) {
	int y;
	int start;
	int length;
	int lines;

	// Pintar el marco
	paintBox(g);

	// Precalculo.
	y = calcInitCordY();
	lines = calcVisibleLines();

	// Pintar las lineas
	start = calcStartView();
	while (lines > 0) {
	    // Longitud de la linea
	    length = calcLengthLine(start);
	    // Posición del cursor
	    if ((m_CursorPos > start) && (m_CursorPos < start + length)) {
		// Guardar la posición del cursor.
		m_CursorX = font.calcLength(m_Buffer, start, m_CursorPos);
		m_CursorY = y;
		// Aqui se podría pintar el cursor ;-)
	    }
	    // Pintar la linea
	    font.drawString(g, x, y, m_Buffer, start, length);
	    // Descontar lineas
	    lines--;
	    // Siguiente linea
	    y += font.getHeight(); 
	    length += 1;
	    start += length;
	}

	// Pintar el prompt
	paintCursor(g);
    }

    protected boolean dispatchEvent(final Msg msg) {
	boolean ret = false;
	switch (msg.getEventCode()) {
	case EventListener.KEY_PRESSED:
	    // El usuario presionó un tecla.
	    ret = dispatchEventKeyPressed(msg.getWParam());
	    break;
	case EventListener.KEY_RELEASED:
	    // El usuario soltó una tecla.
	    ret = dispatchEventKeyReleased(msg.getWParam());
	    break;
	case EventListener.RENDERER:
	    // Framework indica repintar.
	    dispatchEventRenderer();
	    ret = true;
	}
	return ret;
    }
    
    private boolean dispatchEventKeyPressed(int key) {
	boolean ret = true;
	m_Pressed = true;
	switch (key) {
	case Gui.KEY_LEFT:
	    // Mover el cursor a la izquierda
	    moveCursor(-1);
	    break;
	case Gui.KEY_RIGHT:
	    // Mover el cursor a la derecha
	    moveCursor(1);
	    break;
	case Gui.KEY_CLEAR:
	    // Elminar caracter
	    killChar();
	    break;
	case Gui.KEY_0: 
	case Gui.KEY_1: 
	case Gui.KEY_2: 
	case Gui.KEY_3: 
	case Gui.KEY_4: 
	case Gui.KEY_5: 
	case Gui.KEY_6: 
	case Gui.KEY_7: 
	case Gui.KEY_8: 
	case Gui.KEY_9:
	    // Si la edicón está permitida ...
	    if ((getProperties() & PROP_READONLY) == 0) {
		// ... y si está en modo númérico ...
		if ((getProperties() & PROP_NUMERIC) > 0) {
		    // ... siempre mover el cursor ...
		    moveCursor(1);
		    // ... y luego, tipear un digito.
		    typeDigit(key, true);
		}
		else { 
		    // ... pero, si el modo es alfanumérico ...
		    // ... y si la tecla presionada es diferente a la última ...
		    if (m_LastKey != key) {
			// entonces estamos por tipear el primer caracter asociado
			m_CharPos = 0;
			// ... mover el cursor una posición.
			moveCursor(1);
			// ... y luego, typear un caracter.
			typeChar(key, true);
		    }
		    else {
			// ... en caso contrario cambiar el caracter.
			typeChar(key, false);
		    }
		}
	    }
	    break;
	default:
	    ret = false;
	}
	return ret;
    }

    private boolean dispatchEventKeyReleased(int key) {
	m_Pressed = false;
	return true;
    }

    private void dispatchEventRenderer() {
	// Si hay una tecla presionada ...
	if (m_Pressed) {
	    // ... el cursor desaparece ...
	    m_Cursor = false;
	    // ... el control edit, tiene una función especial
	    switch (m_LastKey) {
	    case Gui.KEY_LEFT:
		moveCursor(-1);
		break;
	    case Gui.KEY_RIGHT:
		moveCursor(1);
		break;
	    case Gui.KEY_CLEAR:
		killChar();
		break;
	    case Gui.KEY_0: 
	    case Gui.KEY_1: 
	    case Gui.KEY_2: 
	    case Gui.KEY_3: 
	    case Gui.KEY_4: 
	    case Gui.KEY_5: 
	    case Gui.KEY_6: 
	    case Gui.KEY_7: 
	    case Gui.KEY_8: 
	    case Gui.KEY_9:
		// Si la edicón está permitida ...
		if ((getProperties() & PROP_READONLY) == 0) {
		    // Cambiar el caracter a dígito.
		    typeDigit(m_LastKey, false);
		}
		break;
	    }
	}
	else {
	    if (focus()) {
		m_Cursor = ((getProperties() & PROP_HIDECURSOR) > 0) ? false : !m_Cursor;
	    }
	    if (m_CharPos > 0) {
		m_Counter++;
		if (m_Counter > CHAR_TIMEOUT) {
		    m_LastKey = Gui.KEY_NONE;
		    m_CharPos = 0;
		    m_Counter = 0;
		    moveCursor(1);
		}
	    }
	}
    }

    private void moveCursor(int inc) {
	m_CursorPos += inc;
	// Límite inferior es cero.
	m_CursorPos = (m_CursorPos < 0) ? 0 : m_CursorPos;
	// Límite superior puede ser igual al número de caracteres en el buffer.
	m_CursorPos = (m_CursorPos > m_Buffer.length()) ? m_Buffer.length() : m_CursorPos;
	// Si me posiciono sobre el caracter de control ...
	while (isLinefeed(m_Buffer.charAt(m_CursorPos))) {
	    // ... moverse al caracter adyacente.
	    m_CursorPos = (inc < 0) ? (m_CursorPos - 1) : (m_CursorPos + 1);
	}
    }

    private void killChar() {
	if (m_CursorPos > 0) {
	    moveCursor(-1);
	    m_Buffer.deleteCharAt(m_CursorPos);
	    adjust(m_CursorPos);
	}
    }

    private void typeChar(int code, boolean insert) {
	char[] charset;
	char car;
	charset = m_Keypad[code - Gui.KEY_0];
	m_CharPos = (m_CharPos == charset.length) ? 0 : m_CharPos;
	car = charset[m_CharPos++];
	editChar(car, insert);
    }

    private void typeDigit(int code, boolean insert) {
	char[] charset;
	char car;
	charset = m_Keypad[code - Gui.KEY_0];
	car = charset[charset.length - 1];
	editChar(car, insert);
    }

    private void editChar(char car, boolean insert) {
	if (insert) {
	    // Si estamos al final del buffer ...
	    if (m_CursorPos == m_Buffer.length()) {
		// ... añadimos el caracter
		m_Buffer.append(car);
	    }
	    else {
		// ... insertamos el caracter.
		m_Buffer.insert(m_CursorPos, car);
	    }
	    // Ajustar texto al control.
	    adjust(m_CursorPos);
	}
	else {
	    // ... cambiamos el caracter.
	    m_Buffer.setCharAt(m_CursorPos, car);
	}

    }

    /**
     * FUNCIONES PARA PARA AJUSTAR EL TEXTO A LA REPRESENTACIÓN GRÁFICA.
     * 
     */

    /**
     * Indica si el caracter significa un salto de linea.
     * 
     * @return Si el caracter significa un salto de linea devuelve true
     *         en caso contrario, devuelve false.
     */
    private boolean isLinefeed(char c) {
	int i = 0;
	while (i < m_Linefeed.length) {
	    if (c == m_Linefeed[i++]) {
		return true;
	    }
	}
	return false;
    }

    /** 
     * Indica la posición del primer caracter visible del control.
     * 
     * 
     * @return Devuelve un indice válido para el objeto m_Buffer.
     */
    private int calcStartView() {
	int ret;
	int lines;
	// Precalculo.
	ret = m_CursorPos;	// Posición actual del cursor
	lines = calcVisibleLines();	// Número de lineas.
	// Mientras no se llegue al principio del archivo,
	// y le número el control soporte varias lineas.
	while ((ret > 0) && (lines > 0)) {
	    // Si el caracter es salto de linea ...
	    if (isLinefeed(m_Buffer.charAt(ret))) {
		// ... descontar lineas
		lines--;
	    }
	    ret--;
	}
	// Si nos detemos por número de lineas ...
	if (ret > 0) {
	    // reubicamos para saltar caracter de control.
	    ret += 2;
	}
	return ret;
    }

    /** 
     * Indica la posición del primer caracter de una linea.
     * 
     * @param charOffset Posición de cualquier caracter que pertenece a la linea.
     * 
     * @return Devuelve un indice válido para el objeto m_Buffer.
     */
    private int calcStartLine(int charOffset) {
	while(charOffset > 0) {
	    if (isLinefeed(m_Buffer.charAt(charOffset--))) {
		charOffset++;
	    }
	}
	return charOffset;
    }

    /** 
     * Devuelve la longitud de una linea que entra en el ancho del control
     * 
     * @param start Indice donde empieza la linea.
     * 
     * @return Longitud de la linea. Se mide hasta encontrar un caracter
     *         de control o hasta terminar el buffer.
     */
    private int calcLengthLine(int start) {
	int len = 0;

	while (start + len < m_Buffer.length()) {
	    if (isLinefeed(m_Buffer.charAt(start + len))) {
		len--;
		break;
	    }
	    len++;
	}

	return len;
    }

    /** 
     * Ajusta los caracteres al area del control.
     * 
     * @param charOffset Indice del primer caracter a procesar.
     */
    private void adjust(int charOffset) {
	int start;
	int i;
	start = calcStartLine(charOffset);
	i = 0;
	// Mientras no llegue al final del buffer ...
	while (start + i < m_Buffer.length()) {
	    // ... si encontramos un caracter de control ...
	    if (m_Buffer.charAt(start + i) == CHAR_CONTROL) {
		// ... borrarlo, reasignaremos su posición.
		m_Buffer.deleteCharAt(start + i);
		// Decrementar el contador de lineas
		m_Lines--;
	    }
	    else if (isLinefeed(m_Buffer.charAt(start + i))) {
		// ... si corresponde a un salto de linea
		// Calcular la nueva posición de start
		i++;
		start += i;
		// Reiniciar la longitud de linea a ajustar.
		i = 0;
		m_Lines++;
		// Incrementar el contador de lineas
		m_Lines++;
	    } 
	    else{
		// ... y si es otro caracter, y ya tenemos una linea 
		// para ajustar ...
		if (font.calcLength(m_Buffer, start, i) > getViewWidth()) {
		    // ... insertar un caracter de control.
		    m_Buffer.insert(start + i, CHAR_CONTROL);
		    // Incrementar el contador de lineas
		    m_Lines++;
		    // Calcular la nueva posición de start
		    i++;
		    start += i;
		    // Reiniciar la longitud de linea a ajustar.
		    i = 0;
		}
		else {
		    // ... en caso contrario, procesar otro caracter.
		    i++;
		}
	    }
	}
    }

    /**
     * FUNCIONES AUXILIARES DE LA REPRESENTACION GRÁFICA.
     * 
     */
    private int calcVisibleLines() {
	return getViewHeight() / font.getHeight();
    }

    private int calcInitCordY() {
	if ((getProperties() & PROP_VCENTER) > 0) {
	    if ((m_Lines > 0) && (m_Lines < calcVisibleLines())) {
		return (calcVisibleLines() - m_Lines)*font.getHeight()/2;
	    }
	}
	return y;
    }

    protected void paintBox(final Graphics g) {
        // Dibujar rectangulo
        g.setColor(0);
        g.drawRect(x + CTRL_HMARGIN, 
		   y + CTRL_VMARGIN, 
		   getWidth() - CTRL_HMARGIN, 
		   getHeight() - CTRL_HMARGIN);
	// Dibujar efectos por focus.
//         if(focus()) {
//             g.setColor(GuiCtrl.COLORFOCUS2);
//             g.drawLine( (x+dx), (y+1), (x+dx), (y+dy-1) );
//             g.drawLine( (x+1), (y+dy), (x+dx), (y+dy) );
            
//             g.setColor(GuiCtrl.COLORFOCUS);
//             g.fillRect( x+1, y+1, dx-2, dy-2 );
//         }
    }

    protected void paintCursor(final Graphics g) {
        if(m_Cursor) {
	    font.drawString(g, m_CursorX, m_CursorY, (char)FontSystem.CURSOR);
        }
    }

    /**
     * FUNCIONES PARA EDICIÓN DE CONTENIDO.
     * 
     */
    public int addText(char[] buffer, int start, int length) {
	int i;
	int len;
	i = 0;
	len = 0;
	while (i < length) {
	    if (buffer[start + i] != CHAR_CONTROL) {
		editChar(buffer[start + i], true);
		len++;
	    }
	    i++;
	}
	len += m_Buffer.length();
	return len;
    }

    public int addText(char[] buffer) {
	return addText(buffer, 0, buffer.length);
    }

    public int addText(final String string) {
	return setText(string.toCharArray());
    }

    public int addText(final StringBuffer buffer) {
	int i;
	int len;
	char c;
	i = 0;
	len = 0;
	while (i < buffer.length()) {
	    c = buffer.charAt(i);
	    if (c != CHAR_CONTROL) {
		editChar(c, true);
		len++;
	    }
	    i++;
	}
	len += m_Buffer.length();
	return len;
    }

    public int setText(char[] buffer, int start, int length) {
	m_Buffer.setLength(0);
	return addText(buffer, start, length);
    }

    public int setText(char[] buffer) {
	return setText(buffer, 0, buffer.length);
    }

    public int setText(final String string) {
	m_Buffer.setLength(0);
	return addText(string);
    }

    public int setText(final StringBuffer buffer) {
	m_Buffer.setLength(0);
	return addText(buffer);
    }

    public int getText(StringBuffer buffer) {
	char c;
	int i = 0;
	int len = 0;
	while (i < m_Buffer.length()) {
	    c = m_Buffer.charAt(i);
	    if (c != CHAR_CONTROL) {
		buffer.append(c);
		len++;
	    }
	    i++;
	}
	return len;
    }

    public int getCount() {
	int len = 0;
	int i = 0;
	while (i < m_Buffer.length()) {
	    if (m_Buffer.charAt(i) != CHAR_CONTROL) {
		len++;
	    }
	    i++;
	}
	return len;
    }
}
